package com.blah.gwtgames.client.common.ui;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;

public class Slider extends Widget implements SourcesChangeEvents {
	private Element track;
	private HTML thumb;
	private boolean hasMouseCapture;
	private int minValue;
	private int maxValue;
	private int value;
	
	private int revertValue;
	private static final int DRAGGING_REVERT_DISTANCE = 50;
	
	private ChangeListenerCollection changeListeners = null;
	
	public Slider()
	{
		this(0, 100);
	}
	
	public Slider(int minValue, int maxValue)
	{
		super();
		
		value = 0;
		hasMouseCapture = false;
		
		track = DOM.createDiv();
		DOM.setStyleAttribute(track, "backgroundColor", "#aaa");
		DOM.setStyleAttribute(track, "width", "100px");
		DOM.setStyleAttribute(track, "height", "8px");
		
		thumb = new HTML();
		Element thumbElement = thumb.getElement();
		DOM.setStyleAttribute(thumbElement, "backgroundColor", "#888");
		DOM.setStyleAttribute(thumbElement, "width", "8px");
		DOM.setStyleAttribute(thumbElement, "height", "8px");
		
		setThumb(thumb);
		setElement(track);
		sinkEvents(Event.MOUSEEVENTS);
		setStyleName("gwt-SliderTrack");
		
		setScale(minValue, maxValue);
	}
	
	public void setScale(int minValue, int maxValue)
	{
		if (minValue >= maxValue)
			throw new IllegalArgumentException("Minimum value is not less than the maximum value.");
		
		this.minValue = minValue;
		this.maxValue = maxValue;
		setValue(value); // This clamps the value and resets the slider position.
	}
	
	public void setValue(int value)
	{
		if (value < minValue)
			value = minValue;
		
		if (value > maxValue)
			value = maxValue;
		
		this.value = value;
		
		float ratio = (float)(value - minValue) / (float)(maxValue - minValue);
		int trackWidth = DOM.getIntStyleAttribute(track, "width");
		int trackLeft = DOM.getAbsoluteLeft(track);
		int thumbWidth = DOM.getIntStyleAttribute(thumb.getElement(), "width") / 2;
		
		DOM.setStyleAttribute(thumb.getElement(), "left",
			((ratio * (trackWidth - thumbWidth)) + trackLeft) + "px");
		
		if (changeListeners != null)
			changeListeners.fireChange(this);
	}

	public int getMinValue() {return minValue;}
	public int getMaxValue() {return maxValue;}
	public int getValue() {return value;}
	
	public void setThumb(HTML thumb)
	{
		this.thumb = thumb;
		Element thumbElement = thumb.getElement();
		DOM.appendChild(track, thumbElement);
		DOM.setStyleAttribute(thumbElement, "position", "absolute");
	}
	
	public void onBrowserEvent(Event event)
	{
		switch(DOM.eventGetType(event))
		{
		case Event.ONMOUSEDOWN:
			DOM.setCapture(track);
			hasMouseCapture = true;
			revertValue = value;
			onDrag(event);
			break;
		case Event.ONMOUSEMOVE:
			if (hasMouseCapture)
				onDrag(event);
			break;
		case Event.ONMOUSEUP:
			DOM.releaseCapture(track);
			hasMouseCapture = false;
			break;
		}
	}

	private void onDrag(Event event)
	{
		int trackHeight = DOM.getIntStyleAttribute(track, "height");
		int yOffset = DOM.eventGetClientY(event) - DOM.getAbsoluteTop(track);
		if (yOffset < -DRAGGING_REVERT_DISTANCE || yOffset > (trackHeight + DRAGGING_REVERT_DISTANCE))
		{
			setValue(revertValue);
			return;
		}
		
		int trackWidth = DOM.getIntStyleAttribute(track, "width");
		int trackLeft = DOM.getAbsoluteLeft(track);
		int thumbWidth = DOM.getIntStyleAttribute(thumb.getElement(), "width");
		int x = DOM.eventGetClientX(event) - trackLeft - (thumbWidth / 2);
		
		float ratio = (float)x/(float)(trackWidth - thumbWidth);
		
		if (ratio < 0.0f)
			ratio = 0.0f;
		
		if (ratio > 1.0f)
			ratio = 1.0f;
		
		value = (int)(ratio * (maxValue - minValue) + minValue);
		
		DOM.setStyleAttribute(thumb.getElement(), "left",
			(ratio * (trackWidth - thumbWidth) + trackLeft) + "px");
		
		if (changeListeners != null)
			changeListeners.fireChange(this);
	}

	protected void onLoad()
	{
		// The thumb (position: absolute) tends to get lost
		// in space when it's not attached to the page. Resetting
		// the value puts it in the right place again.
		setValue(value);
	}

	public void addChangeListener(ChangeListener listener) {
		if (changeListeners == null)
			changeListeners = new ChangeListenerCollection();
		
		changeListeners.add(listener);
	}

	public void removeChangeListener(ChangeListener listener) {
		if (changeListeners != null)
			changeListeners.remove(listener);
	}

}
